﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Dot5Demo.Encodings
{
    public class EncodingUtil
    {
        private const char KVSplitChar = ' ';
        private const char PPSplitChar = '\n';
        private const char ObjSplitChar = '\t';

        public static string EncodingEx(object t, string prefix = "")
        {
            if (t == null)
                return null;
            var ty = t.GetType();
            var builder = new StringBuilder();
            builder.AppendLine(prefix + ty.Name);
            var mems = ty.FindMembers(MemberTypes.Field | MemberTypes.Property, BindingFlags.Public | BindingFlags.Instance, null, null);
            if (mems != null)
            {
                for (int i = 0; i < mems.Length; i++)
                {
                    var m = mems[i];
                    if (m.MemberType != MemberTypes.Field && m.MemberType != MemberTypes.Property)
                        continue;
                    object value = null;
                    Type mType = null;
                    if (m is FieldInfo)
                    {
                        var f = m as FieldInfo;
                        value = f.GetValue(t);
                        mType = f.FieldType;
                    }
                    else if (m is PropertyInfo)
                    {
                        var p = m as PropertyInfo;
                        mType = p.PropertyType;
                        if (p.CanRead)
                            value = p.GetValue(t);
                    }
                    if (value != null && mType != null)
                    {
                        if (mType.IsValueType
                            || mType.IsPrimitive
                            || mType.IsEnum
                            || mType == typeof(string))
                        {
                            builder.AppendLine(prefix + string.Format("\t{0} {1}", m.Name, value.ToString()));
                        }
                        else
                        {
                            builder.Append(EncodingEx(value, prefix + "\t"));
                        }
                    }
                }
            }
            builder.AppendLine(prefix + ty.Name);
            return builder.ToString();
        }

        private static Dictionary<Guid, List<MemberInfo>> TypeMembers = new Dictionary<Guid, List<MemberInfo>>();
        private static Dictionary<Module, Dictionary<MemberInfo, bool>> IgnoreMembers = new Dictionary<Module, Dictionary<MemberInfo, bool>>();
        private static Dictionary<MemberInfo, string> MemProps = new Dictionary<MemberInfo, string>();


        public static string Encoding<T>(T t)
        {
            if (t == null)
                return null;
            if (HasEncodingIgnore(typeof(T)))
                return null;

            return null;
        }

        private static void Add(Type t, bool r)
        {
            if (!IgnoreMembers.ContainsKey(t.Module))
                IgnoreMembers.Add(t.Module, new Dictionary<MemberInfo, bool>());
            IgnoreMembers[t.Module].Add(t, r);
        }

        private static void Add(MemberInfo t, bool r)
        {
            if (!IgnoreMembers.ContainsKey(t.Module))
                IgnoreMembers.Add(t.Module, new Dictionary<MemberInfo, bool>());
            IgnoreMembers[t.Module].Add(t, r);
        }

        private static void Process(Type t)
        {
            if (t == null)
                return;
            var r = HasEncodingIgnore(t);
            if (r)
            {
                return;
            }
            if (TypeMembers.ContainsKey(t.GUID))
                return;
            TypeMembers.Add(t.GUID, null);
            var mems = t.FindMembers(MemberTypes.Field | MemberTypes.Property, BindingFlags.Public | BindingFlags.Instance, null, null);
            if (mems == null)
            {
                Add(t, false);
                return;
            }
            var list = new List<MemberInfo>();
            for (int i = 0; i < mems.Length; i++)
            {
                var m = mems[i];
                var ret = HasEncodingIgnore(m);
                GetEncodingProperty(m);
                if (ret)
                {
                    list.Add(m);
                }
            }
            TypeMembers[t.GUID] = list;
        }

        private static bool HasEncodingIgnore(MemberInfo t)
        {
            if (t == null)
                return false;
            if (IgnoreMembers.ContainsKey(t.Module))
            {
                if (IgnoreMembers[t.Module].ContainsKey(t))
                    return IgnoreMembers[t.Module][t];
            }
            Dictionary<MemberInfo, bool> d;
            if (!IgnoreMembers.ContainsKey(t.Module))
            {
                d = new Dictionary<MemberInfo, bool>();
            }
            else
            {
                d = IgnoreMembers[t.Module];
            }
            var r = HasEncodingIgoreAttr(t);
            d.Add(t, r);
            return r;
        }

        private static bool HasEncodingIgoreAttr(MemberInfo t)
        {
            if (t == null)
                return false;
            var r = t.GetCustomAttributes(typeof(EncodingIgnoreAttribute), false);
            return r != null && r.Length > 0;
        }

        private static string GetEncodingProperty(MemberInfo t)
        {
            if (t == null)
                return null;
            if (MemProps.ContainsKey(t))
            {
                return MemProps[t];
            }
            if (HasEncodingPropertyAttr(t, out EncodingPropertyAttribute attr))
            {
                var v = t.Name;
                if (!string.IsNullOrEmpty(attr.Name))
                {
                    v = attr.Name;
                }
                MemProps.Add(t, v);
                return v;
            }
            else
            {
                MemProps.Add(t, t.Name);
                return t.Name;
            }
        }

        private static bool HasEncodingPropertyAttr(MemberInfo t, out EncodingPropertyAttribute attr)
        {
            attr = null;
            if (t == null)
                return false;
            var r = t.GetCustomAttributes(typeof(EncodingPropertyAttribute), false);
            if (r != null && r.Length > 0)
            {
                attr = r[0] as EncodingPropertyAttribute;
                return true;
            }
            return false;
        }
    }
}
